/*
    Copyright (c) 2012 Denis Mingulov.

    This file is part of Drop Cache.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <meegotouch/MApplicationWindow>
#include <meegotouch/MApplication>

#include <meegotouch/MContainer>
#include <meegotouch/MLayout>
#include <meegotouch/MButton>
#include <meegotouch/MLabel>
#include <meegotouch/MMessageBox>
#include <meegotouch/MGridLayoutPolicy>
#include <meegotouch/MLinearLayoutPolicy>
#include <meegotouch/MProgressIndicator>
#include <meegotouch/MBanner>
#include <QtCore/QSettings>
#include <QtCore/QPointer>
#include <QtGui/QGraphicsLinearLayout>
#include <QtGui/QGraphicsGridLayout>
#include <QtGui/QTextOption>
#include <QtCore/QProcess>
#include <QtCore/QHash>
#include <QtCore/QFile>

#include <unistd.h>

#include <QDebug>

#include "dropcachewidget.h"

#include "translations.h"

namespace Constants
{
    const int MemDivider = 1024;

    static const QLatin1String DropCacheApp("/opt/dropcache-mdn/bin/dropcache.sh");

    static const QLatin1String MemFree("MemFree");
    static const QLatin1String MemTotal("MemTotal");
    static const QLatin1String SwapFree("SwapFree");
    static const QLatin1String SwapTotal("SwapTotal");
}

// ----------------------------------------------------
class DropCacheWidgetPrivate
{
public:
    DropCacheWidgetPrivate() {}

    int updateMemoryState();
    QHash<QString, int> getMemoryInfo() const;

    QPointer<MLabel> infoMemoryLabel;
    QPointer<MLabel> infoSwapLabel;
    QPointer<MProgressIndicator> infoMemoryProgressBar;
    QPointer<MProgressIndicator> infoSwapProgressBar;
};

int DropCacheWidgetPrivate::updateMemoryState()
{
    QHash<QString, int> dict = getMemoryInfo();

    const int divider = Constants::MemDivider;

    int memFree = dict.value(Constants::MemFree, 0) / divider;
    int memTotal = dict.value(Constants::MemTotal, 0) / divider;
    int swapFree = dict.value(Constants::SwapFree, 0) / divider;
    int swapTotal = dict.value(Constants::SwapTotal, 0) / divider;

    //qDebug() << memFree << memTotal << swapFree << swapTotal;

    if (infoMemoryLabel)
        infoMemoryLabel->setText(qtTrId("Memory: free %1 of %2 mb").arg(memFree).arg(memTotal));
    if (infoMemoryProgressBar)
    {
        infoMemoryProgressBar->setRange(0, memTotal);
        infoMemoryProgressBar->setValue(memFree);
    }

    if (infoSwapLabel)
        infoSwapLabel->setText(qtTrId("Swap: free %3 of %4 mb").arg(swapFree).arg(swapTotal));
    if (infoSwapProgressBar)
    {
        infoSwapProgressBar->setRange(0, swapTotal);
        infoSwapProgressBar->setValue(swapFree);
    }

    return 0;
}

QHash<QString, int> DropCacheWidgetPrivate::getMemoryInfo() const
{
    QHash<QString, int> dict;

    QFile file(QLatin1String("/proc/meminfo"));
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
        return dict;

    QTextStream in(&file);

    // ensure to read less than 1000 lines
    for (int i = 0; i < 1000; i++)
    {
        QString line = in.readLine().trimmed();
        if (line.length() <= 0)
            break;

        int pos = line.indexOf(QChar(':'));
        if (pos <= 0)
            break;

        // name of parameter
        QString name = line.left(pos).trimmed();

        QString strValue = line.mid(pos + 1).trimmed();
        int numEnd = strValue.indexOf(QChar(' '));
        if (numEnd >= 0)
            strValue = strValue.left(numEnd).trimmed();
        int value = strValue.toInt();
        //qDebug() << "2nd part" << strValue;
        //qDebug() << "pos = " << pos << " name = " << name << " value = " << value;

        dict[name] = value;
    }

    file.close();

    return dict;
}

// -----------------------------------------------------

DropCacheWidget::DropCacheWidget(QGraphicsItem *parent) :
    MStylableWidget(parent),
    d_ptr(new DropCacheWidgetPrivate)
{
    createContent();

    if (style()->autoRefreshInfoOnDisplayEnterEvent())
    {
        qDebug() << "Auto refresh enabled (on display entered event)";

        // update memory state when appears on the screen
        if (MApplicationWindow *win = MApplication::activeApplicationWindow())
        {
            // prevent data update if on the switcher only - but update app if it will be chosen in the switcher
            qDebug() << "Window is available";
            connect(win, SIGNAL(displayEntered()), SLOT(handleDisplayEvent()), Qt::QueuedConnection);
            connect(win, SIGNAL(switcherExited()), SLOT(handleDisplayEvent()), Qt::QueuedConnection);
        }
    }
}

DropCacheWidget::~DropCacheWidget()
{
}

void DropCacheWidget::createContent()
{
    MLayout *layout = new MLayout();
    layout->setContentsMargins(0, 0, 0, 0);

    MLinearLayoutPolicy *portraitPolicy = new MLinearLayoutPolicy(layout, Qt::Vertical);
    portraitPolicy->setSpacing(0);

    MGridLayoutPolicy *landscapePolicy = new MGridLayoutPolicy(layout);
    landscapePolicy->setSpacing(0);

    // header
    QGraphicsLayoutItem *header = createHeader();
    if (header)
    {
        portraitPolicy->addItem(header);
        landscapePolicy->addItem(header, 0, 0, 1, 2);
    }

    // info
    MContainer *info = createInfoContainer();
    if (info)
    {
        portraitPolicy->addItem(info);
        //landscapePolicy->addItem(info, 1, 0, 1, 2);
        landscapePolicy->addItem(info, 1, 0);

        // workaround for the equal columns in grid mode
        info->setPreferredWidth(1);
    }

    // sync
    MContainer *sync = createSyncContainer();
    if (sync)
    {
        portraitPolicy->addItem(sync);
        //landscapePolicy->addItem(sync, 2, 0);
        landscapePolicy->addItem(sync, 2, 0);

        // workaround for the equal columns in grid mode
        sync->setPreferredWidth(1);
    }

    // cache
    MContainer *cache = createCacheContainer();
    if (cache)
    {
        portraitPolicy->addItem(cache);
        //landscapePolicy->addItem(cache, 2, 1);
        landscapePolicy->addItem(cache, 1, 1, 2, 1);

        // workaround for the equal columns in grid mode
        cache->setPreferredWidth(1);
    }

    // last stretch
    portraitPolicy->addItem(createSpacer());
    landscapePolicy->addItem(createSpacer(), 3, 0, 1, 2);

    // setup policies to layout
    layout->setLandscapePolicy(landscapePolicy);
    layout->setPortraitPolicy(portraitPolicy);

    setLayout(layout);
}

QGraphicsLayoutItem *DropCacheWidget::createHeader() const
{
    MContainer *container = createEmptyContainer(style()->titleContainerStyleName(), false);

    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Horizontal);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

    MLabel *label = new MLabel();
    label->setStyleName(style()->titleHeaderStyleName());
    label->setText(qtTrId(Loc::AppTitle));

    layout->addItem(label);

    layout->addItem(createSpacer());

    MButton *button = new MButton();
    connect(button, SIGNAL(clicked()), SLOT(clickedInfo()), Qt::QueuedConnection);
    button->setViewType(MButton::iconType);
    button->setIconID(style()->titleInfoIcon());
    button->setStyleName(style()->titleInfoIconStyleName());
    layout->addItem(button);
    layout->setAlignment(button, Qt::AlignRight | Qt::AlignVCenter);

    container->centralWidget()->setLayout(layout);

    return container;
}

MContainer *DropCacheWidget::createEmptyContainer(const QString &containerStyle, bool headerVisible) const
{
    MContainer *container = new MContainer;
    container->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    container->setContentsMargins(0, 0, 0, 0);
    container->setStyleName(containerStyle);
    container->setHeaderVisible(headerVisible);

    return container;
}

QGraphicsLayoutItem *DropCacheWidget::createSpacer(const QString &style) const
{
    MWidgetController *spacer = new MWidgetController;
    spacer->setViewType(QLatin1String("background"));
    if (!style.isEmpty())
        spacer->setStyleName(style);
    else
        spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    return spacer;
}

MContainer *DropCacheWidget::createCacheContainer()
{
    MContainer *container = createEmptyContainer(style()->containerStyleName());
    container->setTitle(qtTrId(Loc::CacheTitle));

    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

    // spacer
    layout->addItem(createSpacer(style()->smallSpacerStyleName()));

    // label
    MLabel *label = new MLabel(qtTrId(Loc::AboutCache));
    label->setStyleName(style()->usualDescriptionLabelStyleName());
    label->setWordWrap(true);
    label->setWrapMode(QTextOption::WordWrap);
    layout->addItem(label);

    // spacer
    layout->addItem(createSpacer(style()->usualSpacerStyleName()));

    // 2 buttons
    MContainer *buttonContainer = createEmptyContainer(style()->usualButtonsPanelStyleName());
    buttonContainer->setHeaderVisible(false);

    QGraphicsLinearLayout *buttonLayout = new QGraphicsLinearLayout(Qt::Horizontal);
    buttonLayout->setContentsMargins(0, 0, 0, 0);
    buttonLayout->setSpacing(0);

    MButton *buttonLeft = new MButton();
    connect(buttonLeft, SIGNAL(clicked()), SLOT(doDrop1()), Qt::QueuedConnection);
    buttonLeft->setStyleName(style()->usualLeftButtonStyleName());
    buttonLeft->setText(qtTrId(Loc::FreePageCache));

    buttonLayout->addItem(buttonLeft);

    MButton *buttonRight = new MButton();
    connect(buttonRight, SIGNAL(clicked()), SLOT(doDrop2()), Qt::QueuedConnection);
    buttonRight->setStyleName(style()->usualRightButtonStyleName());
    buttonRight->setText(qtTrId(Loc::FreeInodes));

    buttonLayout->addItem(buttonRight);

    buttonContainer->centralWidget()->setLayout(buttonLayout);
    layout->addItem(buttonContainer);

    // spacer
    layout->addItem(createSpacer(style()->smallSpacerStyleName()));

    // 3rd (bottom center) button
    MButton *buttonCenter = new MButton();
    connect(buttonCenter, SIGNAL(clicked()), SLOT(doDrop3()), Qt::QueuedConnection);
    buttonCenter->setStyleName(style()->usualCenterButtonStyleName());
    buttonCenter->setText(qtTrId(Loc::FreeAll));

    layout->addItem(buttonCenter);
    layout->setAlignment(buttonCenter, Qt::AlignCenter);

    if (style()->showSyncAndFreeButton())
    {
        qDebug() << "Show 'do all' button";

        // spacer
        layout->addItem(createSpacer(style()->smallSpacerStyleName()));

        // highlighted "do all" button
        MButton *buttonAll = new MButton();
        connect(buttonAll, SIGNAL(clicked()), SLOT(doSyncAndDrop()), Qt::QueuedConnection);
        buttonAll->setStyleName(style()->accentedCenterButtonStyleName());
        buttonAll->setText(qtTrId(Loc::DoSyncAndFree));

        layout->addItem(buttonAll);
        layout->setAlignment(buttonAll, Qt::AlignCenter);
    }

    // end spacer
    layout->addItem(createSpacer());

    container->centralWidget()->setLayout(layout);
    return container;
}

MContainer *DropCacheWidget::createSyncContainer()
{
    MContainer *container = createEmptyContainer(style()->containerStyleName());
    container->setTitle(qtTrId(Loc::SyncTitle));

    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

    // spacer
    layout->addItem(createSpacer(style()->smallSpacerStyleName()));

    // label
    MLabel *label = new MLabel(qtTrId(Loc::AboutSync));
    label->setStyleName(style()->usualDescriptionLabelStyleName());
    label->setWordWrap(true);
    label->setWrapMode(QTextOption::WordWrap);
    layout->addItem(label);

    // spacer
    layout->addItem(createSpacer(style()->usualSpacerStyleName()));

    // 3rd (bottom center) button
    MButton *buttonCenter = new MButton();
    connect(buttonCenter, SIGNAL(clicked()), SLOT(doSync()), Qt::QueuedConnection);
    buttonCenter->setStyleName(style()->usualCenterButtonStyleName());
    buttonCenter->setText(qtTrId(Loc::DoSync));

    layout->addItem(buttonCenter);
    layout->setAlignment(buttonCenter, Qt::AlignCenter);

    // spacer
    //layout->addItem(createSpacer(style()->usualSpacerStyleName()));

    container->centralWidget()->setLayout(layout);
    return container;
}

MContainer *DropCacheWidget::createInfoContainer()
{
    MContainer *container = createEmptyContainer(style()->containerStyleName());
    container->setTitle(qtTrId(Loc::InfoTitle));

    QGraphicsLinearLayout *layout = new QGraphicsLinearLayout(Qt::Vertical);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);

    // spacer
    layout->addItem(createSpacer(style()->smallSpacerStyleName()));

    // label
    MLabel *label = new MLabel(qtTrId(Loc::AboutInfo));
    label->setStyleName(style()->usualDescriptionLabelStyleName());
    label->setWordWrap(true);
    label->setWrapMode(QTextOption::WordWrap);
    layout->addItem(label);

    // spacer
    layout->addItem(createSpacer(style()->usualSpacerStyleName()));

    // memory state
    // memory info
    d_ptr->infoMemoryLabel = new MLabel();
    d_ptr->infoMemoryLabel->setStyleName(style()->infoMemoryLabelStyleName());
    d_ptr->infoMemoryLabel->setWordWrap(true);
    d_ptr->infoMemoryLabel->setWrapMode(QTextOption::WordWrap);
    layout->addItem(d_ptr->infoMemoryLabel);

    d_ptr->infoMemoryProgressBar = new MProgressIndicator(container, MProgressIndicator::barType);
    d_ptr->infoMemoryProgressBar->setStyleName(style()->infoMemoryProgressBarStyleName());
    layout->addItem(d_ptr->infoMemoryProgressBar);

    // swap info
    d_ptr->infoSwapLabel = new MLabel();
    d_ptr->infoSwapLabel->setStyleName(style()->infoSwapLabelStyleName());
    d_ptr->infoSwapLabel->setWordWrap(true);
    d_ptr->infoSwapLabel->setWrapMode(QTextOption::WordWrap);
    layout->addItem(d_ptr->infoSwapLabel);

    d_ptr->infoSwapProgressBar = new MProgressIndicator(container, MProgressIndicator::barType);
    d_ptr->infoSwapProgressBar->setStyleName(style()->infoSwapProgressBarStyleName());
    layout->addItem(d_ptr->infoSwapProgressBar);

    // update the content
    doInfoRefresh();

    // spacer
    layout->addItem(createSpacer(style()->usualSpacerStyleName()));

    // 3rd (bottom center) button
    MButton *buttonCenter = new MButton();
    connect(buttonCenter, SIGNAL(clicked()), SLOT(doInfoRefresh()), Qt::QueuedConnection);
    buttonCenter->setStyleName(style()->usualCenterButtonStyleName());
    buttonCenter->setText(qtTrId(Loc::DoInfoRefresh));

    layout->addItem(buttonCenter);
    layout->setAlignment(buttonCenter, Qt::AlignCenter);

    // spacer
    //layout->addItem(createSpacer(style()->usualSpacerStyleName()));

    container->centralWidget()->setLayout(layout);
    return container;
}

void DropCacheWidget::execApp(const QString &name, const QStringList &param)
{
    QHash<QString, int> oldMemoryState;
    if (style()->showSyncDoneBanner())
        oldMemoryState = d_ptr->getMemoryInfo();

    qDebug() << "Start" << name << param;

    QProcess app;
    app.start(name, param);
    app.waitForFinished();

    qDebug() << "Finish";

    doInfoRefresh();

    if (style()->showSyncDoneBanner())
    {
        //! \todo 2nd read of memory info, 1st - at doInfoRefresh, optimize somehow
        QHash<QString, int> currentMemoryState = d_ptr->getMemoryInfo();

        int memoryAdded = currentMemoryState.value(Constants::MemFree, 0) - oldMemoryState.value(Constants::MemFree, 0);
        int swapAdded = currentMemoryState.value(Constants::SwapFree, 0) - oldMemoryState.value(Constants::SwapFree, 0);

        if (memoryAdded < 0)
            memoryAdded = 0;
        if (swapAdded < 0)
            swapAdded = 0;

        memoryAdded /= Constants::MemDivider;
        swapAdded /= Constants::MemDivider;

        // show info banner
        MBanner *banner = new MBanner();
        banner->setStyleName(MBannerType::InformationBanner);
        if (memoryAdded <= 0 && swapAdded <= 0)
            banner->setTitle(qtTrId("Done, no visible effect"));
        else
            banner->setTitle(qtTrId("Done, free memory increase %1 mb, swap %2 mb").arg(memoryAdded).arg(swapAdded));
        banner->appear(scene(), MSceneWindow::DestroyWhenDone);
    }
}

void DropCacheWidget::doDrop1()
{
    execApp(Constants::DropCacheApp, QStringList() << QLatin1String("--1"));
}

void DropCacheWidget::doDrop2()
{
    execApp(Constants::DropCacheApp, QStringList() << QLatin1String("--2"));
}

void DropCacheWidget::doDrop3()
{
    execApp(Constants::DropCacheApp, QStringList() << QLatin1String("--3"));
}

void DropCacheWidget::doSync()
{
    ::sync();
}

void DropCacheWidget::doSyncAndDrop()
{
    doSync();
    doDrop3();
}

void DropCacheWidget::doInfoRefresh()
{
    qDebug() << "Info refresh";

    d_ptr->updateMemoryState();
}

void DropCacheWidget::handleDisplayEvent()
{
    MApplicationWindow *win = MApplication::activeApplicationWindow();
    if (!win)
    {
        qDebug() << "No active window";
        return;
    }

    if (win->isInSwitcher())
    {
        qDebug() << "In switcher - do nothing";
        return;
    }

    qDebug() << "Refresh due to display event";
    doInfoRefresh();
}

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)

void DropCacheWidget::clickedInfo()
{
    MMessageBox box(qtTrId(Loc::AppTitle) + QLatin1Char(' ') + QLatin1String(TOSTRING(APPVER)), QString::fromUtf8("© 2012 Denis Mingulov"));
    box.exec();
}
